package com.massfords.jaxb;

import java.util.Set;

import com.sun.codemodel.JBlock;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;

/**
 * Creates a traversing visitor. This visitor pairs a visitor and a traverser. The result is a visitor that 
 * will traverse the entire graph and visit each of the nodes using the provided visitor.
 * 
 * @author markford
 */
public class CreateTraversingVisitorClass extends CodeCreator {

    private JDefinedClass mVisitor;
    private JDefinedClass mTraverser;

    public CreateTraversingVisitorClass(JDefinedClass aVisitor, JDefinedClass aTraverser, Outline aOutline, JPackage aPackage) {
        super(aOutline, aPackage);
        mVisitor = aVisitor;
        mTraverser = aTraverser;
    }

    @Override
    protected void run(Set<ClassOutline> aClasses) {

        JDefinedClass traversingVisitor = getOutline().getClassFactory().createClass(getPackage(), "TraversingVisitor", null);
        traversingVisitor._implements(mVisitor);
        JMethod ctor = traversingVisitor.constructor(JMod.PUBLIC);
        ctor.param(mTraverser, "aTraverser");
        ctor.param(mVisitor, "aVisitor");
        JFieldVar fieldVisitor = traversingVisitor.field(JMod.PRIVATE, mVisitor, "visitor");
        JFieldVar fieldTraverser = traversingVisitor.field(JMod.PRIVATE, mTraverser, "traverser");
        addGetterAndSetter(traversingVisitor, fieldVisitor);
        addGetterAndSetter(traversingVisitor, fieldTraverser);
        ctor.body().assign(fieldTraverser, JExpr.ref("aTraverser"));
        ctor.body().assign(fieldVisitor, JExpr.ref("aVisitor"));
        
        setOutput(traversingVisitor);
        
        for(ClassOutline classOutline : aClasses) {
            // add method impl to traversing visitor
            JMethod travViz = traversingVisitor.method(JMod.PUBLIC, void.class, "visit");
            JVar beanVar = travViz.param(classOutline.implClass, "aBean");
            JBlock travVizBloc = travViz.body();
            travVizBloc.invoke(beanVar, "accept").arg(JExpr.invoke("getVisitor"));
            travVizBloc.invoke(JExpr.invoke("getTraverser"), "traverse").arg(beanVar).arg(JExpr._this());
        }
    }

    /**
     * Convenience method to add a getter and setter method for the given field.
     * 
     * @param aTraversingVisitor
     * @param aField
     */
    private void addGetterAndSetter(JDefinedClass aTraversingVisitor, JFieldVar aField) {
        String propName = Character.toUpperCase(aField.name().charAt(0)) + aField.name().substring(1);
        aTraversingVisitor.method(JMod.PUBLIC, aField.type(), "get" + propName).body()._return(aField);
        JMethod setVisitor = aTraversingVisitor.method(JMod.PUBLIC, void.class, "set" + propName);
        JVar visParam = setVisitor.param(aField.type(), "aVisitor");
        setVisitor.body().assign(aField, visParam);
    }
}
